package org.zn.note.jdk.others;

import sun.misc.Signal;
import sun.misc.SignalHandler;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/*
这个问题是SpringBoot启停脚本引出的。
就是kill pid，默认发送15号信号，程序可以捕捉处理，可以做收尾工作、释放资源。
但是有的脚本里是kill -9 pid，直接干掉进程了。

关于收尾工作：
1、线程池。可以调用ExecutorService.shutdown，不接收新任务，但是现有任务处理完；也可以shutdownNow(=thread.interrupt)直接中断。
2、socket链接，比如netty、mq
3、告诉注册中心快速下线
4、清理临时文件，比如poi
5、释放对内对外内存
强行杀进程，可能使程序或者数据在一个中间状态，造成数据丢失或者不一致

另外SpringBoot可以通过spring-boot-starter-actuator：curl -X POST http://127.0.0.1:8088/shutdown关机

*/

/*
Linux C 捕捉信号
#include <stdio.h>
#include <signal.h>
#include<unistd.h>

void myHandler(int signum){
        printf("捕获到信号 %d \n",signum);
}

int main(){
        void(* handler)=myHandler;
        signal(SIGINT,handler);
        signal(SIGTERM,handler);
        while(1){
                printf("进程运行中...\n");
                sleep(1);
        }
}
 */

/*
关于 nohub 和 &

使用&后台运行程序：
结果会输出到终端
使用Ctrl + C发送SIGINT信号，程序免疫
关闭session发送SIGHUP信号，程序关闭

使用nohup运行程序：
结果默认会输出到nohup.out
使用Ctrl + C发送SIGINT信号，程序关闭
关闭session发送SIGHUP信号，程序免疫

平日线上经常使用nohup和&配合来启动程序：
同时免疫SIGINT和SIGHUP信号

同时，还有一个最佳实践：
不要将信息输出到终端标准输出，标准错误输出，而要用日志组件将信息记录到日志里
 */

/*
Linux 查看支持的信号
1、命令 kill -l
2、看头文件 /usr/include
find . -name "*.h"|xargs grep SIGKILL 找头文件
 */

/**
 * Jvm有默认的信号处理 {@link SignalHandler}
 */
public class SignalTest {
    public static void main(String[] args) {
        // 判断当前操作系统，windows和linux的信号名称有不同，可以用这个判断。
        System.getProperties().getProperty("os.name").toLowerCase().startsWith("win");
        // 设置Ctrl+c信号。应该是SIGINT，但是这里不用写SIG
        Signal sig = new Signal("INT");
        // 实例化成员内部类前，要先实例化成员内部类所在的外部类
        SignalTest signalTest = new SignalTest();
        // 成员内部类实例化的方法：外部类对象.new
        Signal.handle(sig, signalTest.new Handler());


        // 试一下对于RuntimeException、OutOfMemery，Hook能处理不
        signalTest.registerShutdownHook();

        /**
         * 试一下OutOfMemery是影响线程，还是把JVM搞挂，Hook能处理不
         * 结论：OutOfMemery仅仅影响当前线程，所以同RuntimeException是一样的情形
         * 注意这块在试验的时候要把java内存设置小一点：java -Xms8M -Xmx8M xxxx
         */
        new Thread() {
            public void run() {
                List list = new LinkedList();
                while (true) {
                    list.add("adsfadsfadsfadsfasdf");
                }
            }
        }.start();

        for (int i = 0; i < 100; i++) {
            System.out.println(i);
            try {
                if (i == 5) {
                    // 试一下对于RuntimeException结束的，Hook能处理不：结论是能处理
                    // throw new RuntimeException();
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private class Handler implements SignalHandler {
        @Override
        public void handle(Signal signal) {
            System.out.println("Catch Signal: " + signal.getName() + ", Thread Name: " + Thread.currentThread().getName());
            // 如果捕捉信号后要退出程序，这里可以进行一些善后工作
            // 也可以在这里注册一个Hook(钩子)线程来处理
            // 钩子线程处理过程中，其他线程仍然在运行，并不是说把其他线程都关了，只跑钩子线程
            registerShutdownHook();
            /**
             * java进程结束，返回一个状态给上层启动java进程的进程（父进程）
             */
            // System.exit(0);
            /**
             * 试一试RuntimeException异常关闭的时候，Hook能处理不
             * 注意，java处理信号是起一个新线程处理！也就是其他线程都挂住，然后执行这个Handler线程
             * 而抛出RuntimeException仅仅是把当前Handler线程弄挂了，对Main线程没有影响
             * 由于Main线程还在，JVM并没有挺，所以并不会触发Hook
             */
            // throw new RuntimeException();
        }
    }

    private void registerShutdownHook() {
        Thread t = new Thread(new ShutdownHook(), "ShutdownHook-Thread");
        Runtime.getRuntime().addShutdownHook(t);
    }

    private class ShutdownHook implements Runnable {
        @Override
        public void run() {
            System.out.println("ShutdownHook execute start ... ");
            try {
                // 钩子线程里不要调用
                // System.exit(0); 会卡住JVM的关闭过程
                // 但可以调用
                // Runtime.getRuntime().halt(0); // 强制关闭，和kill -9一样
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("ShutdownHook execute end ... ");
        }
    }
}
